#include "stdafx.h"
#include "Mesh.h"
#include "Exception.h"
#include "Log.h"

namespace Cvekas {

#pragma pack(1)
struct CmfHeader
{
	char id[3];
	byte version;
	uint vertex_offset;
	uint index_offset;
};

struct Cmf1VertexDesc 
{
	byte has_position;
	byte has_color;
	byte has_normal;
	byte has_tangent;
	byte num_uvs;
	byte vertex_size;
	uint vertex_count;
};

struct Cmf1IndexDesc
{
	byte is16bit;
	uint index_count;
};
#pragma pack()

Mesh::Mesh(const std::string& path)
{
	LOG(FORMAT("Loading mesh from file %s", % path));

	std::ifstream input(path.c_str(), std::ios::binary);
	CmfHeader header;
	input.read(reinterpret_cast<char*>(&header), sizeof(header));

	if(header.id[0] != 'C' || header.id[1] != 'M' || header.id[2] != 'F')
		throw EXCEPTION("Trying to load an invalid cmf file");

	switch(header.version)
	{
	case 1:
		{
		size_t start = path.find_last_of('\\');
		size_t end = path.find_last_of('.');
		if(start == std::string::npos)
			start = -1;
		cmf1_load(input, path.substr(start+1, end-start-1), header.vertex_offset, header.index_offset);
		break;
		}
	default:
		throw EXCEPTION("Unknown cmf file version");
	}
}

Mesh::Mesh(VertexBufferPtr vb, IndexBufferPtr ib)
: vertex_buffer(vb), index_buffer(ib)
{
}

void Mesh::cmf1_load(std::ifstream& input, const std::string& name, uint vertex_offset, uint index_offset)
{
	Engine e;
	GpuBufferManagerPtr gpu_buffer_manager = e.getVideoDevice()->getGpuBufferManager();

	Cmf1VertexDesc vertex_desc;

	input.seekg(vertex_offset, std::ios_base::beg);

	input.read(reinterpret_cast<char*>(&vertex_desc), sizeof(vertex_desc));
	VertexFormat format(true, vertex_desc.num_uvs, vertex_desc.has_normal != 0, false, vertex_desc.has_tangent != 0,
		vertex_desc.has_color != 0);
	assert(format.getSize() == vertex_desc.vertex_size);

	vertex_buffer = gpu_buffer_manager->createVertexBuffer(name + "VB", format, vertex_desc.vertex_count, false);
	LockedBuffer locked_vb = vertex_buffer->lock(false);
	input.read(reinterpret_cast<char*>(locked_vb.data), locked_vb.size);
	vertex_buffer->unlock();

	if(index_offset != 0)
	{
		Cmf1IndexDesc index_desc;

		input.seekg(index_offset, std::ios_base::beg);

		input.read(reinterpret_cast<char*>(&index_desc), sizeof(index_desc));
		index_buffer = gpu_buffer_manager->createIndexBuffer(name + "IB", index_desc.is16bit != 0, 
			index_desc.index_count, false);
		LockedBuffer locked_ib = index_buffer->lock(false);
		input.read(reinterpret_cast<char*>(locked_ib.data), locked_ib.size);
		index_buffer->unlock();
	}
}

RenderableMesh::RenderableMesh(MeshPtr mesh, EffectPtr effect, EffectParamsPtr effect_params, uint priority, uint passes)
: mesh(mesh), effect(effect), effect_params(effect_params), passes(passes), priority(priority), 
  position(0.0f, 0.0f, 0.0f), rotation(0.0f, 0.0f, 0.0f), scale(1.0f, 1.0f, 1.0f)
{
	if(!bool(mesh))
		throw EXCEPTION("Invalid mesh provided to RenderableMesh constructor");
	if(!bool(effect))
		throw EXCEPTION("Invalid effect provided to RenderableMesh constructor");

	VertexBufferPtr vertex_buffer = mesh->getVertexBuffer();
	IndexBufferPtr index_buffer = mesh->getIndexBuffer();
	
	batches = new Batch[passes];
	for(uint i = 0; i < passes; ++i)
	{
		batches[i].priority = priority;
		batches[i].batch_id = i;
		batches[i].vb_id = vertex_buffer->getId();
		batches[i].ib_id = bool(index_buffer) ? index_buffer->getId() : IndexBuffer::INVALID_ID;
		batches[i].effect_id = effect->getId();
		batches[i].pass = i;
		batches[i].start_index = 0;
		if(bool(index_buffer))
			batches[i].tri_count = index_buffer->getIndexCount() / 3;
		else
			batches[i].tri_count = vertex_buffer->getVertexCount() / 3;
		batches[i].owner = this;
	}
}

RenderableMesh::~RenderableMesh()
{
	delete[] batches;
}

void RenderableMesh::onPreRender(uint16 batch_id)
{
	world = Matrix::scale(scale) * Matrix::rotate(rotation) * Matrix::translate(position);
	
	Engine e;
	e.getVideoDevice()->getRenderingParams().setPerBatch(world, batch_id);
	effect->applyAutoParams();

	if(bool(effect_params))
		effect_params->applyParams();
	effect->commitChanges();
}

uint RenderableMesh::getBatches(Batch** batches)
{
	*batches = this->batches;
	return passes;
}

} // namespace 